﻿
Imports System.IO
Imports Microsoft.WindowsAPICodePack.Dialogs

Public Class Window1

    Private _originalRootNode As FileInfoWrapper
    Private _currentFilter As FileStatus? = Nothing


    Private Enum FileStatus
            Same
            Different
            NewFile
            Deleted
        End Enum

        Private Class FileInfoWrapper
            Public Property Name As String
            Public Property FullPath As String
            Public Property Status As FileStatus
            Public Property Size As Long
            Public Property ModifiedDate As DateTime
            Public Property Foreground As Brush
            Public Property Children As New List(Of FileInfoWrapper)
        Public Property Extension As String
        Public Property RelativePath As String

        Public Sub New(name As String, fullPath As String, status As FileStatus)
                Me.Name = name
                Me.FullPath = fullPath
                Me.Status = status
                Me.Extension = Path.GetExtension(fullPath).ToLower()

                Select Case status
                    Case FileStatus.Same
                    Foreground = Brushes.Black
                Case FileStatus.Different
                        Foreground = Brushes.Red
                    Case FileStatus.NewFile
                        Foreground = Brushes.Blue
                    Case FileStatus.Deleted
                        Foreground = Brushes.Gray
                End Select
            End Sub
        End Class

        Private availableExtensions As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
        Private selectedExtensions As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)

        Private Sub btnBrowse1_Click(sender As Object, e As RoutedEventArgs)
        Dim folderBrowserDialog = New System.Windows.Forms.FolderBrowserDialog()
        If folderBrowserDialog.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
            txtFolder1.Text = folderBrowserDialog.SelectedPath
        End If

    End Sub

        Private Sub btnBrowse2_Click(sender As Object, e As RoutedEventArgs)
        Dim folderBrowserDialog = New System.Windows.Forms.FolderBrowserDialog()
        If folderBrowserDialog.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
            txtFolder2.Text = folderBrowserDialog.SelectedPath
        End If
    End Sub

        Private Sub btnCompare_Click(sender As Object, e As RoutedEventArgs)
            If String.IsNullOrWhiteSpace(txtFolder1.Text) OrElse String.IsNullOrWhiteSpace(txtFolder2.Text) Then
                MessageBox.Show("请选择两个要比较的文件夹", "提示", MessageBoxButton.OK, MessageBoxImage.Warning)
                Return
            End If

            If Not Directory.Exists(txtFolder1.Text) OrElse Not Directory.Exists(txtFolder2.Text) Then
                MessageBox.Show("指定的文件夹不存在", "错误", MessageBoxButton.OK, MessageBoxImage.Error)
                Return
            End If

            lblStatus.Text = "正在比较文件夹..."
            tvResults.Items.Clear()

        ' 在后台线程中执行比较操作，避免UI冻结
        'Task.Factory.StartNew(Sub() CompareFolders(txtFolder1.Text, txtFolder2.Text))

        CompareFolders(txtFolder1.Text, txtFolder2.Text)

    End Sub

    Private Sub CompareFolders(folder1Path As String, folder2Path As String)
        availableExtensions.Clear()
        Dim folder1Files = GetAllFiles(folder1Path)
        Dim folder2Files = GetAllFiles(folder2Path)

        ' 收集所有文件扩展名
        CollectExtensions(folder1Files)
        CollectExtensions(folder2Files)

        ' 更新UI - 显示扩展名复选框
        Dispatcher.Invoke(Sub() UpdateExtensionFilters())

        ' 找出相对路径
        Dim allRelativePaths = folder1Files.Keys.Union(folder2Files.Keys).Distinct().OrderBy(Function(p) p).ToList()

        Dim rootNode As New FileInfoWrapper("比较结果", "", FileStatus.Same)

        For Each relativePath In allRelativePaths
            Dim pathParts = relativePath.Split(Path.DirectorySeparatorChar)
            Dim currentNode As FileInfoWrapper = rootNode

            ' 添加文件节点
            Dim fileName = pathParts.Last()
            Dim fileStatus As FileStatus
            Dim fileInfo As FileInfo = Nothing
            Dim fullPath As String = ""

            ' 构建目录树
            For i = 0 To pathParts.Length - 2
                Dim dirName = pathParts(i)
                Dim existingDir = currentNode.Children.FirstOrDefault(Function(c) c.Name = dirName AndAlso c.Children.Any())

                If existingDir Is Nothing Then
                    existingDir = New FileInfoWrapper(dirName, "", fileStatus.Same)
                    currentNode.Children.Add(existingDir)
                End If

                currentNode = existingDir
            Next

            If folder1Files.ContainsKey(relativePath) AndAlso folder2Files.ContainsKey(relativePath) Then
                Dim file1 = folder1Files(relativePath)
                Dim file2 = folder2Files(relativePath)

                'If file1.Length = file2.Length AndAlso file1.LastWriteTime = file2.LastWriteTime Then
                '    fileStatus = FileStatus.Same
                'Else
                '    fileStatus = FileStatus.Different
                'End If

                Debug.Print(file1.Name)

                If FilesAreEqual2(file1.FullName, file2.FullName) Then
                    fileStatus = FileStatus.Same
                Else
                    fileStatus = FileStatus.Different
                End If


                fullPath = file2.FullName
                fileInfo = file2
            ElseIf folder1Files.ContainsKey(relativePath) Then
                fileStatus = FileStatus.Deleted
                fullPath = folder1Files(relativePath).FullName
                fileInfo = folder1Files(relativePath)
            Else
                fileStatus = FileStatus.NewFile
                fullPath = folder2Files(relativePath).FullName
                fileInfo = folder2Files(relativePath)
            End If

            Dim fileNode As New FileInfoWrapper(fileName, fullPath, fileStatus)
            If fileInfo IsNot Nothing Then
                fileNode.Size = fileInfo.Length
                fileNode.ModifiedDate = fileInfo.LastWriteTime
                fileNode.Extension = fileInfo.Extension.ToLower()
            End If

            currentNode.Children.Add(fileNode)
        Next

        _originalRootNode = rootNode

        ' 更新UI
        Dispatcher.Invoke(Sub()
                              PopulateTreeView(rootNode)
                              lblStatus.Text = $"比较完成 - 文件夹1: {folder1Files.Count} 文件, 文件夹2: {folder2Files.Count} 文件"
                          End Sub)
    End Sub

    Private Function FilesAreEqual(path1 As String, path2 As String) As Boolean
        ' 首先检查文件是否存在
        If Not File.Exists(path1) OrElse Not File.Exists(path2) Then
            Return False
        End If

        ' 直接读取全部内容比较
        Dim content1 = File.ReadAllText(path1)
        Dim content2 = File.ReadAllText(path2)

        Debug.Print(content1.Length)
        Debug.Print(content2.Length)

        Debug.Print(content1)
        Debug.Print(content2)


        Return content1 = content2
    End Function

    Private Function FilesAreEqual2(path1 As String, path2 As String) As Boolean
        If Not File.Exists(path1) OrElse Not File.Exists(path2) Then
            Return False
        End If

        '' 先比较文件大小，不同则直接返回false
        'Dim file1Info = New FileInfo(path1)
        'Dim file2Info = New FileInfo(path2)
        'If file1Info.Length <> file2Info.Length Then
        '    Return False
        'End If

        ' 逐行比较
        Using reader1 As New StreamReader(path1),
          reader2 As New StreamReader(path2)

            While True
                Dim line1 = reader1.ReadLine()
                Dim line2 = reader2.ReadLine()

                ' 到达文件末尾
                If line1 Is Nothing AndAlso line2 Is Nothing Then
                    Return True
                End If

                ' 行内容不同
                If line1 <> line2 Then
                    Debug.Print(line1)
                    Debug.Print(line2)
                    Return False
                End If
            End While
        End Using
    End Function

    Private Sub CollectExtensions(files As Dictionary(Of String, FileInfo))
            For Each file In files.Values
                Dim ext = file.Extension.ToLower()
                If Not String.IsNullOrEmpty(ext) Then
                    availableExtensions.Add(ext)
                End If
            Next
        End Sub

        Private Sub UpdateExtensionFilters()
            wrapFileTypes.Children.Clear()

            ' 添加"全选"复选框
            Dim chkAll As New CheckBox()
            chkAll.Content = "全选"
            chkAll.IsChecked = True
            chkAll.Margin = New Thickness(5)
            AddHandler chkAll.Checked, AddressOf chkAll_Checked
            AddHandler chkAll.Unchecked, AddressOf chkAll_Checked
            wrapFileTypes.Children.Add(chkAll)

            ' 添加所有扩展名复选框
            For Each ext In availableExtensions.OrderBy(Function(e) e)
                Dim chk As New CheckBox()
                chk.Content = ext
                chk.IsChecked = True
                chk.Margin = New Thickness(5)
                chk.Tag = ext
                AddHandler chk.Checked, AddressOf chkExtension_CheckedChanged
                AddHandler chk.Unchecked, AddressOf chkExtension_CheckedChanged
                wrapFileTypes.Children.Add(chk)
                selectedExtensions.Add(ext)
            Next
        End Sub

        Private Sub chkAll_Checked(sender As Object, e As RoutedEventArgs)
            Dim chkAll = CType(sender, CheckBox)
            For Each child In wrapFileTypes.Children
                If TypeOf child Is CheckBox AndAlso child IsNot sender Then
                    Dim chk = CType(child, CheckBox)
                    chk.IsChecked = chkAll.IsChecked
                End If
            Next
        End Sub

    Private Sub chkExtension_CheckedChanged(sender As Object, e As RoutedEventArgs)
        Dim chk = CType(sender, CheckBox)
        Dim ext = chk.Tag.ToString()

        If chk.IsChecked Then
            selectedExtensions.Add(ext)
        Else
            selectedExtensions.Remove(ext)
        End If

        ' 禁用所有复选框避免重复操作
        SetCheckBoxesEnabled(False)

        ' 在后台线程执行过滤以避免UI冻结
        Task.Factory.StartNew(Sub()
                                  Dispatcher.Invoke(Sub() FilterTreeView())
                                  Dispatcher.Invoke(Sub() SetCheckBoxesEnabled(True))
                              End Sub)
    End Sub

    Private Sub SetCheckBoxesEnabled(enabled As Boolean)
        For Each child In wrapFileTypes.Children
            If TypeOf child Is CheckBox Then
                CType(child, CheckBox).IsEnabled = enabled
            End If
        Next
    End Sub

    Private Sub FilterTreeView()
        If _originalRootNode Is Nothing Then Return

        ' 基于原始数据构建过滤后的树
        Dim filteredRoot = BuildFilteredTree(_originalRootNode)
        PopulateTreeView(filteredRoot)
    End Sub

    'Private Function BuildFilteredTree(sourceNode As FileInfoWrapper) As FileInfoWrapper
    '    Dim filteredNode = New FileInfoWrapper(
    '    sourceNode.Name,
    '    sourceNode.FullPath,
    '    sourceNode.Status) With {
    '    .Extension = sourceNode.Extension,
    '    .RelativePath = sourceNode.RelativePath,
    '    .Size = sourceNode.Size,
    '    .ModifiedDate = sourceNode.ModifiedDate
    '}

    '    ' 处理子节点
    '    For Each child In sourceNode.Children
    '        ' 如果是文件夹
    '        If child.Children.Any() Then
    '            Dim filteredChild = BuildFilteredTree(child)
    '            If filteredChild.Children.Any() Then ' 只有包含可见子节点才添加
    '                filteredNode.Children.Add(filteredChild)
    '            End If
    '        Else
    '            ' 如果是文件，检查扩展名
    '            If ShouldIncludeFile(child) Then
    '                filteredNode.Children.Add(New FileInfoWrapper(
    '                child.Name,
    '                child.FullPath,
    '                child.Status) With {
    '                .Extension = child.Extension,
    '                .RelativePath = child.RelativePath,
    '                .Size = child.Size,
    '                .ModifiedDate = child.ModifiedDate
    '            })
    '            End If
    '        End If
    '    Next

    '    Return filteredNode
    'End Function

    'Private Function ShouldIncludeFile(fileNode As FileInfoWrapper) As Boolean
    '    ' 无扩展名的文件始终包含
    '    If String.IsNullOrEmpty(fileNode.Extension) Then Return True

    '    ' 检查扩展名是否在选中列表中（不区分大小写）
    '    Return selectedExtensions.Contains(fileNode.Extension.ToLower())
    'End Function

    'Private Function BuildFilteredTree() As FileInfoWrapper
    '    ' 获取原始根节点
    '    Dim originalRoot = If(tvResults.Items.Count > 0,
    '                     CType(CType(tvResults.Items(0), TreeViewItem).Tag, FileInfoWrapper),
    '                     Nothing)
    '    If originalRoot Is Nothing Then Return Nothing

    '    ' 创建新的过滤后的根节点
    '    Dim filteredRoot = New FileInfoWrapper(originalRoot.Name, originalRoot.FullPath, originalRoot.Status)

    '    ' 递归过滤子节点
    '    For Each child In originalRoot.Children
    '        FilterNode(child, filteredRoot)
    '    Next

    '    Return filteredRoot
    'End Function

    Private Sub FilterNode(sourceNode As FileInfoWrapper, parentNode As FileInfoWrapper)
        ' 如果是文件夹
        If sourceNode.Children.Any() Then
            Dim filteredFolder = New FileInfoWrapper(sourceNode.Name, sourceNode.FullPath, sourceNode.Status)

            ' 递归处理子节点
            For Each child In sourceNode.Children
                FilterNode(child, filteredFolder)
            Next

            ' 如果文件夹有可见子节点，则添加到树中
            If filteredFolder.Children.Any() Then
                parentNode.Children.Add(filteredFolder)
            End If
        Else
            ' 如果是文件，检查是否在选中的扩展名中
            If String.IsNullOrEmpty(sourceNode.Extension) OrElse
           selectedExtensions.Contains(sourceNode.Extension) Then
                parentNode.Children.Add(New FileInfoWrapper(
                sourceNode.Name, sourceNode.FullPath, sourceNode.Status) With {
                .Size = sourceNode.Size,
                .ModifiedDate = sourceNode.ModifiedDate,
                .Extension = sourceNode.Extension,
                .RelativePath = sourceNode.RelativePath
            })
            End If
        End If
    End Sub

    Private Function SelectNodeByPath(items As ItemCollection, path As String) As Boolean
        For Each itemObj In items
            Dim item = CType(itemObj, TreeViewItem)
            Dim node = CType(item.Tag, FileInfoWrapper)

            If node.FullPath = path Then
                item.IsSelected = True
                item.BringIntoView()
                Return True
            End If

            If SelectNodeByPath(item.Items, path) Then
                item.IsExpanded = True
                Return True
            End If
        Next

        Return False
    End Function

    Private Function FilterTreeViewItem(item As TreeViewItem) As Boolean
            Dim node = CType(item.Tag, FileInfoWrapper)
            Dim hasVisibleChildren = False

            ' 处理子项
            For i = item.Items.Count - 1 To 0 Step -1
                Dim childItem = CType(item.Items(i), TreeViewItem)
                If FilterTreeViewItem(childItem) Then
                    hasVisibleChildren = True
                Else
                    item.Items.RemoveAt(i)
                End If
            Next

            ' 如果是文件夹，只要有可见子项就显示
            If node.Children.Any() Then
                Return hasVisibleChildren
            End If

            ' 如果是文件，检查扩展名是否在选中列表中
            Return String.IsNullOrEmpty(node.Extension) OrElse selectedExtensions.Contains(node.Extension)
        End Function

        Private Function GetAllFiles(folderPath As String) As Dictionary(Of String, FileInfo)
            Dim filesDict As New Dictionary(Of String, FileInfo)(StringComparer.OrdinalIgnoreCase)

            Try
                For Each file In Directory.GetFiles(folderPath, "*.*", SearchOption.AllDirectories)
                    Dim fileInfo = New FileInfo(file)
                    Dim relativePath = file.Substring(folderPath.Length).TrimStart(Path.DirectorySeparatorChar)
                    filesDict.Add(relativePath, fileInfo)
                Next
            Catch ex As Exception
                Dispatcher.Invoke(Sub() MessageBox.Show($"读取文件夹时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error))
            End Try

            Return filesDict
        End Function

        Private Sub PopulateTreeView(rootNode As FileInfoWrapper)
            tvResults.Items.Clear()
            For Each child In rootNode.Children
                Dim treeItem = CreateTreeItem(child)
                tvResults.Items.Add(treeItem)
            Next
        End Sub

    Private Function CreateTreeItem(node As FileInfoWrapper) As TreeViewItem
        Dim item As New TreeViewItem()
        item.Header = If(node.Children.Any(),
                             node.Name,
                             $"{node.Name} ({FormatFileSize(node.Size)}, {node.ModifiedDate:yyyy-MM-dd HH:mm:ss})")
        item.Foreground = node.Foreground
        item.Tag = node

        If node.Children.Any() Then
            item.FontWeight = FontWeights.Bold
            For Each child In node.Children
                item.Items.Add(CreateTreeItem(child))
            Next
        End If

        AddHandler item.Selected, AddressOf TreeViewItem_Selected
        Return item
    End Function

    Private Sub TreeViewItem_Selected(sender As Object, e As RoutedEventArgs)
        Dim item = CType(sender, TreeViewItem)
        'Dim node = CType(item.Tag, FileInfoWrapper)
        Dim node = TryCast(item.Tag, FileInfoWrapper)

        ' 如果不是文件节点则返回
        If node.Children.Any() Then Return

        If node Is Nothing OrElse node.Children.Any() Then
            ' 是文件夹节点或无效节点
            txtFile1Details.Text = "请选择文件节点"
            txtFile2Details.Text = ""
            DiffView.OldText = ""
            DiffView.NewText = ""
            Return
        End If

        DiffView.OldText = ""
        DiffView.NewText = ""
        ' 清空现有内容
        txtFile1Details.Text = ""
        txtFile2Details.Text = ""

        ' 获取完整相对路径
        Dim relativePath = GetFullRelativePath(item)


        ' 获取两个文件夹中对应的文件信息
        Dim file1Path = Path.Combine(txtFolder1.Text, relativePath)
        Dim file2Path = Path.Combine(txtFolder2.Text, relativePath)

        'Dim file1Path = node.FullPath
        'Dim file2Path = node.FullPath

        ' 显示文件夹1中的文件详情
        If File.Exists(file1Path) Then
            Dim fileInfo = New FileInfo(file1Path)
            txtFile1Details.Text = $"路径: {file1Path}{Environment.NewLine}" &
                              $"大小: {FormatFileSize(fileInfo.Length)}{Environment.NewLine}" &
                              $"修改日期: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}{Environment.NewLine}" &
                              $"创建日期: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}{Environment.NewLine}" &
                              $"属性: {fileInfo.Attributes}"

            ' 读取文件内容
            Try
                Dim txtcontent = File.ReadAllText(file1Path)
                DiffView.OldText = txtcontent
            Catch ex As Exception

            End Try
        Else
            txtFile1Details.Text = "文件不存在"
        End If

        ' 显示文件夹2中的文件详情
        If File.Exists(file2Path) Then
            Dim fileInfo = New FileInfo(file2Path)
            txtFile2Details.Text = $"路径: {file2Path}{Environment.NewLine}" &
                              $"大小: {FormatFileSize(fileInfo.Length)}{Environment.NewLine}" &
                              $"修改日期: {fileInfo.LastWriteTime:yyyy-MM-dd HH:mm:ss}{Environment.NewLine}" &
                              $"创建日期: {fileInfo.CreationTime:yyyy-MM-dd HH:mm:ss}{Environment.NewLine}" &
                              $"属性: {fileInfo.Attributes}"

            ' 读取文件内容
            Try
                Dim content = File.ReadAllText(file2Path)
                DiffView.NewText = content
            Catch ex As Exception

            End Try

        Else
            txtFile2Details.Text = "文件不存在"
        End If
    End Sub

    Private Function GetFullRelativePath(item As TreeViewItem) As String
        Dim pathParts As New List(Of String)
        Dim currentItem As TreeViewItem = item

        ' 向上遍历所有父节点
        While currentItem IsNot Nothing
            Dim node = TryCast(currentItem.Tag, FileInfoWrapper)
            If node IsNot Nothing Then
                pathParts.Add(node.Name)
            End If
            currentItem = TryCast(currentItem.Parent, TreeViewItem)
        End While

        ' 反转顺序(从根到当前节点)
        pathParts.Reverse()

        ' 排除根节点("比较结果")
        If pathParts.Count > 0 AndAlso pathParts(0) = "比较结果" Then
            pathParts.RemoveAt(0)
        End If

        ' 组合路径
        Return String.Join(Path.DirectorySeparatorChar.ToString(), pathParts)
    End Function


    Private Function FormatFileSize(size As Long) As String
            If size = 0 Then Return "0 B"

            Dim units = {"B", "KB", "MB", "GB", "TB"}
            Dim digitGroups = CInt(Math.Log10(size) / Math.Log10(1024))
            Return $"{size / Math.Pow(1024, digitGroups):F2} {units(digitGroups)}"
        End Function

    Private Sub FilterByStatus(sender As Object, e As MouseButtonEventArgs)
        Dim stackPanel = CType(sender, StackPanel)
        Dim filterTag = stackPanel.Tag.ToString()

        Select Case filterTag
            Case "Same"
                _currentFilter = FileStatus.Same
            Case "Different"
                _currentFilter = FileStatus.Different
            Case "New"
                _currentFilter = FileStatus.NewFile
            Case "Deleted"
                _currentFilter = FileStatus.Deleted
            Case Else
                _currentFilter = Nothing ' 显示全部
        End Select

        ' 应用过滤
        ApplyStatusFilter()
    End Sub

    Private Sub ApplyStatusFilter()
        If _originalRootNode Is Nothing Then Return

        ' 在后台线程执行过滤以避免UI冻结
        Task.Factory.StartNew(Sub()
                                  Dim filteredRoot = BuildFilteredTree(_originalRootNode)

                                  Dispatcher.Invoke(Sub()
                                                        PopulateTreeView(filteredRoot)
                                                        lblStatus.Text = GetStatusFilterText()
                                                    End Sub)
                              End Sub)
    End Sub

    Private Function GetStatusFilterText() As String
        If Not _currentFilter.HasValue Then Return "显示所有文件"

        Select Case _currentFilter.Value
            Case FileStatus.Same : Return "仅显示相同文件"
            Case FileStatus.Different : Return "仅显示不同文件"
            Case FileStatus.NewFile : Return "仅显示新增文件"
            Case FileStatus.Deleted : Return "仅显示删除文件"
            Case Else : Return "显示所有文件"
        End Select
    End Function

    Private Function BuildFilteredTree(sourceNode As FileInfoWrapper) As FileInfoWrapper
        Dim filteredNode = New FileInfoWrapper(
        sourceNode.Name,
        sourceNode.FullPath,
        sourceNode.Status) With {
        .Extension = sourceNode.Extension,
        .RelativePath = sourceNode.RelativePath,
        .Size = sourceNode.Size,
        .ModifiedDate = sourceNode.ModifiedDate
    }

        ' 处理子节点
        For Each child In sourceNode.Children
            ' 如果是文件夹
            If child.Children.Any() Then
                Dim filteredChild = BuildFilteredTree(child)
                If filteredChild.Children.Any() Then ' 只有包含可见子节点才添加
                    filteredNode.Children.Add(filteredChild)
                End If
            Else
                ' 如果是文件，检查状态和扩展名
                If ShouldIncludeFile(child) Then
                    filteredNode.Children.Add(New FileInfoWrapper(
                    child.Name,
                    child.FullPath,
                    child.Status
                    ) With {
                    .Extension = child.Extension,
                    .RelativePath = child.RelativePath,
                    .Size = child.Size,
                    .ModifiedDate = child.ModifiedDate
                })
                End If
            End If
        Next

        Return filteredNode
    End Function

    Private Function ShouldIncludeFile(fileNode As FileInfoWrapper) As Boolean
        ' 检查扩展名过滤
        If selectedExtensions.Count > 0 AndAlso
           Not String.IsNullOrEmpty(fileNode.Extension) AndAlso
           Not selectedExtensions.Contains(fileNode.Extension.ToLower()) Then
            Return False
        End If

        ' 检查状态过滤
        If _currentFilter.HasValue Then
            Return fileNode.Status = _currentFilter.Value
        End If

        Return True
    End Function


End Class
